昨天成功地完成了身分驗證,於是 IdP 系統已經知道使用者是誰了--也就是 subject
的值。下一步要來問使用者,是否要同意(Consent)應用程式的授權請求。
在昨天最後一步的程式如下:
目前的程式碼太亂,所以有做了一次重構,可以參考 Commit。
return Redirect::away($completedRequest->getRedirectTo());
從 Hydra 回應的 CompletedRequest
物件裡,有個欄位是 redirect_to
,這會讓你知道接下來要帶什麼參數回去 Hydra。
回到 Hydra 後,跟身分驗證很像,Hydra 會檢查使用者過去是否有授權過。如果使用者沒有授權過的記錄,或是應用程式要求了以前沒授權過的範圍,這時就輪到 Consent Provider 出馬來跟使用者互動了。
同意請求為 Consent Request 的直譯。
過程與 Login Provider 類似,首先會被轉導至 Consent Provider。設定一樣是存在設定檔裡,欄位是 urls.consent
,下面是設定:
urls:
consent: http://127.0.0.1:8000/oauth2/consent
對應的導頁網址範例如下:
http://127.0.0.1:8000/oauth2/consent?consent_challenge=5678
與登入流程相同,無論之前是否有授權,都一定會轉導至 Consent Provider,讓開發者能夠在這個節點做對應的處理。
Consent Provider 在處理 request 的時候,首先要使用 consent_challenge
跟 Hydra API 取得資訊。
下面是 Laravel + Hydra SDK 的程式碼範例:
public function __invoke(Request $request, AdminApi $adminApi)
{
$consentChallenge = $request->input('consent_challenge');
$consentRequest = $adminApi->getConsentRequest($consentChallenge);
Log::debug('Get consent Request', json_decode((string)$consentRequest, true));
}
透過 Log 可以看得到 $consentRequest
的資訊如下:
{
"acr": "",
"amr": [],
"challenge": "1982b3eb2c774c5b93c667d69719dbef",
"client": {"client_id": "my-rp", "...": "..."},
"context": [],
"login_challenge": "38edf5462fde46e690ccde80a1b97b66",
"login_session_id": "b8a40193-dd7b-44dc-a019-ba14a57e7531",
"oidc_context": [],
"request_url": "http://127.0.0.1:4444/oauth2/auth?client_id=my-rp&...",
"requested_access_token_audience": [],
"requested_scope": [
"openid"
],
"skip": false,
"subject": "1"
}
與 Login Provider 相同,先來看 skip
與 subject
:
skip
代表的使用者在這個裝置上是否曾同意授權過。如果 skip
是 false
的話,需要顯示授權頁,並使用 requested_scope
的資訊顯示使用者需要同意授權的範圍。如果應用程式是第一方的話,有些情境下可能會跳過這一步--因為這個應用程式跟認證系統是同個組織所開發的。如果 skip
是 true
的話,則不應該顯示使用者介面,而是要接受(或拒絕)同意請求。通常應該都要接受,除非有其他充分的理由才會是由系統自動拒絕請求。
其他欄位都跟登入請求相同,像 client
、login_session_id
(登入請求的 session_id)、oidc_context
、request_url
、requested_access_token_audience
都是相同的,這些就不作說明
額外多出來的幾個欄位說明如下:
acr
是 OpenID Connect Core 1.0 - 2. ID Token 的定義,全名為 Authentication Context Class Reference,直譯為「身分驗證上下文類別參考」。它指的是 IdP 在什麼樣的環境下來執行身分驗證,這個內容可以參考 OpenID Connect Extended Authentication Profile (EAP) ACR Values 1.0 協定,雖然還在草稿階段,但設計的方法是可以看一下的,像裡面就有提到 phr
指的是防釣魚(Phishing-Resistant)機制。
amr
是 OpenID Connect Core 1.0 - 2. ID Token 的定義,全名為 Authentication Methods References,直譯為「身分驗證方法參考」。它代表了身分驗證系統如何確認使用者身分的,比方說透過帳號密碼與 OTP 驗證方法,因為有可能會有多個驗證因子(Multi-factor authentication),因此它的格式是 Array,應用程式可以參考這個值來了解 IdP 是如何做身分驗證的。
以 Hydra 的情境來說,acr
與 amr
都是由 Login Provider 處理完身分驗證後再決定內容,並記錄在 AcceptLoginRequest
物件裡傳回 Hydra,只是因為協定定義這個欄位是非必要,所以昨天沒有額外處理這件事。
context
是昨天賣的關子的說明,昨天在 Login Provider 傳入的 context
,會在今天的 Consent Provider 拿到相同的內容。可能有人會想說,用 Session 或 Cookie 就能輕鬆辦到的事,為什麼要用這個方法。原因很簡單:考慮到 Login Provider 與 Consent Provider 是不同服務的時候(Hydra 的設計是考慮到各個 Provider 都是獨立的微服務),網域或背後所能控制的資訊就有可能會有所不同,因此用 context
傳輸特定的內容反而是會很好控制的。
有了欄位資訊後,接下來就能呈現頁面了。這裡就不獻截圖了,由讀者自由想像,文末會附原始碼參考。
跟身分驗證的情境是不同的,使用者可能會接受,也有可能會拒絕應用程式的授權請求,因此這裡就要明確做一個拒絕授權請求的流程。
假設使用者接受同意請求,則跟登入流程類似:
$acceptConsentRequest = new AcceptConsentRequest([
'grantScope' => ['oidc'],
'remember' => false,
'rememberFor' => 0,
'session' => [
'access_token' => [],
'id_token' => [],
],
]);
$completedRequest = $adminApi->acceptConsentRequest($consentChallenge, $acceptConsentRequest);
return Redirect::away($completedRequest->getRedirectTo());
各個欄位的說明如下:
欄位 | 說明 |
---|---|
grant_scope | 使用者同意的範圍,通常都跟應用程式一樣,或是比應用程式要求的還要少。 |
remember | 與 Login Provider 說明相同,這裡會對應到的是 consent_challenge 所取到的 skip ,而不是 login_challenge 裡的 skip |
remember_for | 與 Login Provider 說明相同 |
session | 這裡所設定的資訊,將會與之後發給應用程式的Access Token 或 ID Token 綁定。若是設定到 Access Token 的話,會在執行 Introspection 的時候看到設定的內容;若是設定 ID Token 的話,則是會成為 Claim 的內容。 |
相對地,拒絕同意請求與拒絕登入請求類似,使用相同的 RejectRequest
,但呼叫不同 API 來完成:
$rejectRequest = new RejectRequest([
'error' => 'access_denied',
'errorDescription' => 'The request was rejected by end-user',
]);
$completedRequest = $adminApi->rejectConsentRequest($consentChallenge, $rejectRequest);
return Redirect::away($completedRequest->getRedirectTo());
欄位定義與重送攻擊的說明與 Login Provider 完全相同。
程式完成後,可以再執行一次登入。最後在所有任務都完成之後,就會回到註冊時定義的 callback,目前範例程式只有顯示「拿到身分驗證回應了」幾個字。然後注意網址列要像下面一樣:
http://127.0.0.1:8000/callback?code=Bx88NgWkNCqaThTnzLUkFOy-hPp4vO0F064XdbjtE6w.xFDHdkj14pKvR-kVFgzdeXVmRpF9NifskrcFmmYftoQ&scope=openid&state=1a2b3c4d
關鍵是要有 code
的欄位,看到這個欄位,Hydra 的任務就算完成了。再來就是要做最後的應用程式認證了。
程式碼調整可以參考 GitHub Commit。若執行過程有遇到奇怪問題的話,建議可以把所有服務和資料庫全部移除,然後再重頭建立一次,應該就能執行了。